﻿/*
BSD License

Copyright (C) 2010  Nicolay Mitropolsky <nicolaymitropolsky@gmail.com>

Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:

   1. Redistributions of source code must retain the above copyright notice, this list of
      conditions and the following disclaimer.

   2. Redistributions in binary form must reproduce the above copyright notice, this list
      of conditions and the following disclaimer in the documentation and/or other materials
      provided with the distribution.

THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace foldshot
{
    public enum DiffState { Same, Diff, Aexists, Bexists }

    public class DiffNode<T> where T : File
    {
        private T a;

        public T A
        {
            get { return a; }
            set { a = value; }
        }
        private T b;

        public T B
        {
            get { return b; }
            set { b = value; }
        }

        private DiffState state;

        public DiffState State
        {
            get { return state; }
            set { state = value; }
        }


    }

    public class FileDiffNode : DiffNode<File>
    {
    }

    public class DirectoryDiffNode : DiffNode<Directory>
    {
        private List<FileDiffNode> innerFileNodes = null;

        private bool lazyloading = false;
        private Func<Directory, Directory, Tuple<List<DirectoryDiffNode>, List<FileDiffNode>>> lazyloader = null;

        public Func<Directory, Directory, Tuple<List<DirectoryDiffNode>, List<FileDiffNode>>> LazyLoader
        {
            get { return lazyloader; }
            set
            {
                innerFileNodes = null;
                lazyloading = true;
                lazyloader = value;
            }
        }

        public List<FileDiffNode> InnerFileNodes
        {
            get
            {
                if (innerFileNodes == null && lazyloading)
                {
                    var lr = lazyloader(A, B);
                    innerDirectoryNodes = lr.Item1;
                    innerFileNodes = lr.Item2;
                }

                return innerFileNodes;
            }
            set { innerFileNodes = value; }
        }

        private List<DirectoryDiffNode> innerDirectoryNodes = null;

        public List<DirectoryDiffNode> InnerDirectoryNodes
        {
            get
            {
                if (innerDirectoryNodes == null && lazyloading)
                {
                    var lr = lazyloader(A, B);
                    innerDirectoryNodes = lr.Item1;
                    innerFileNodes = lr.Item2;
                }

                return innerDirectoryNodes;
            }
            set { innerDirectoryNodes = value; }
        }






    }

    //TODO: refactor this
    public class FoldDiff
    {

        private struct ListResult<T>
        {
            public ListResult(List<T> list, bool hasDiffs, bool hasMatched1)
            {
                this.list = list;
                this.hasDiffs = hasDiffs;
                this.hasMatched = hasMatched1;
            }
            public List<T> list;
            public bool hasDiffs;
            public bool hasMatched;

        }

        public FoldDiff()
        {
            DiffOnly = false;
            Pattern = null;
        }

        public bool DiffOnly { get; set; }

        public Regex Pattern { get; set; }


        public DirectoryDiffNode CreateDiffTree(Directory a, Directory b)
        {
            if (a.sort != null && b.sort != null && a.reverse != null && b.reverse != null)
            {
                if (!a.sort.Equals("name") || !b.sort.Equals("name") || !a.reverse.Equals(b.reverse))
                {
                    throw new ArgumentException("incompatible directory sorting");
                }
            }

            return createDiffTree(a, b);
        }

        private DirectoryDiffNode createDiffTree(Directory a, Directory b, DiffState predefinedState = DiffState.Same)
        {
            DirectoryDiffNode result = new DirectoryDiffNode
                                           {
                                               A = a,
                                               B = b
                                           };

            bool isDiff = false;
            bool isMathed = Pattern == null || Pattern.IsMatch(a.name);

            ListResult<FileDiffNode> innerFilesDiff = createFilesDiffNodesList(a, b,predefinedState);

            isDiff = isDiff | innerFilesDiff.hasDiffs;
            isMathed |= innerFilesDiff.hasMatched;

            ListResult<DirectoryDiffNode> innerDirDiff = createDirectoryDiffNodesList(a, b, predefinedState);
            isDiff = isDiff | innerDirDiff.hasDiffs;
            isMathed |= innerDirDiff.hasMatched;

            if (isMathed == false)
                return null;

            if (isDiff)
            {
                result.InnerFileNodes = innerFilesDiff.list;
                result.InnerDirectoryNodes = innerDirDiff.list;
                result.State = DiffState.Diff;
            }
            else
            {
                result.State = predefinedState;
                result.LazyLoader = lazyCreatorFactory(predefinedState);
            }

            return result;
        }


        private Func<Directory, Directory, Tuple<List<DirectoryDiffNode>, List<FileDiffNode>>>
            lazyCreatorFactory(DiffState state)
        {
            return (a, b) =>
                       {

                           ListResult<FileDiffNode> fl = createFilesDiffNodesList(a, b, state);
                           ListResult<DirectoryDiffNode> dl = createDirectoryDiffNodesList(a, b, state);

                           return Tuple.Create(dl.list, fl.list);
                       }; 
        }

        private ListResult<DirectoryDiffNode> createDirectoryDiffNodesList(Directory a, Directory b, DiffState state)
        {
            return ___createDiffNodesList(a.InnerDirectories, b.InnerDirectories, state,
                                           createDiffTree);
        }

        private ListResult<FileDiffNode> createFilesDiffNodesList(Directory a, Directory b, DiffState state)
        {
            return ___createDiffNodesList(a.InnerFiles, b.InnerFiles,state, diffFiles);
        }

     
        private FileDiffNode diffFiles(File x, File y, DiffState predefinedState = DiffState.Same)
        {

            var state = predefinedState;

            if(Pattern != null && !Pattern.IsMatch(x.name))
            {
                return null;
            }

            if (state == DiffState.Same &&
                (!x.lastModified.Equals(y.lastModified)
                ||!x.size.Equals(y.size)))
            {
                state = DiffState.Diff;
            }

            return new FileDiffNode
                       {
                           A = x,
                           B = y,
                           State = state,
                       };
        }

        private ListResult<TN> ___createDiffNodesList<TN, T>(List<T> af, List<T> bf, DiffState predefinedstate, Func<T, T,DiffState, TN> deepcompare)
            where T : File
            where TN : DiffNode<T>, new()
        {
            var fnl = new List<TN>(Math.Max(af.Count, bf.Count));
            var hasDiffs = false;
            var hasMatched = this.Pattern == null;

             

            int ai = 0;
            int bi = 0;

            while (ai < af.Count || bi < bf.Count)
            {
                var x = ai < af.Count ? af[ai]:null;
                var y = bi < bf.Count ? bf[bi]:null;

                if (x!=null && y !=null && x.name.Equals(y.name))
                {
                    TN item = deepcompare(x, y,predefinedstate);
                    if (item != null)
                    {

                        if (!(item.State == DiffState.Same && DiffOnly))
                        {
                            fnl.Add(item);
                        }
                        hasDiffs |= !(item.State == predefinedstate);
                        hasMatched |= true;
                    }
                    ai++;
                    bi++;
                }
                else
                {
                    if (x!=null && (y==null || x.name.CompareTo(y.name) < 0))
                    {

                        var dd = makeHeadingA<TN, T>(x, DiffState.Aexists);

                        if(dd!=null)
                        {
                            fnl.Add(dd);
                            hasDiffs |= true;
                            hasMatched |= true;
                        }
                        
                        ai++;
                    }
                    else
                    {
                        var dd = makeHeadingA<TN, T>(y, DiffState.Bexists);
                        if(dd!=null)
                        {
                            fnl.Add(dd);
                            hasDiffs |= true;
                            hasMatched |= true;
                        }
                        bi++;
                    }

                }
            }

            return new ListResult<TN>(fnl, hasDiffs, hasMatched);
        }

        private TN makeHeadingA<TN, T>(T a, DiffState ds)
            where T : File
            where TN : DiffNode<T>, new()
        {

            bool isMatched = Pattern == null || Pattern.IsMatch(a.name);

            TN result = new TN();
            result.A = a;
            result.B = a;
            result.State = ds;

            //TODO: must not be here
            var dir = result as DirectoryDiffNode;
            if (dir != null)
            {
                dir.LazyLoader = lazyCreatorFactory(ds);

                if(Pattern!=null)
                {
                   var r = createDiffTree(dir.A, dir.B, ds);
                   isMatched |= r != null;
                }

            }

            if (!isMatched)
                return null;

            return result;
        }



    }

}
